package com.genius.workflow.log;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.NamedThreadLocal;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerMapping;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.genius.adminmanager.user.entity.UserJson;
import com.genius.workflow.util.Pattern;
import com.genius.workflow.util.TimeDiff;

/**
 * 日志管理
 * 
 * @author woy
 *
 */
@Aspect
@Component
public class GeniusLogAspect {
	private  Logger logger = LoggerFactory.getLogger(GeniusLogAspect.class);
	private  ThreadLocal<Date> beginTimeThreadLocal = new NamedThreadLocal<Date>("ThreadLocal beginTime");
	private ThreadLocal<Geniuslog> logThreadLocal = new NamedThreadLocal<Geniuslog>("ThreadLocal log");
	private  ThreadLocal<JSONObject> currentUser = new NamedThreadLocal<>("ThreadLocal user");

	@Resource
	@Qualifier("threadPoolTaskExecutor")
	ThreadPoolTaskExecutor threadPoolTaskExecutor;
	@Resource
	 GeniusLogService logService;

	 @Autowired(required=false)
	 HttpServletRequest request;
	/**
	 * Service层切点
	 */
//	@Pointcut("@annotation(com.genius.log.SystemServiceLog)")
//	public void serviceAspect() {
//	}

	@Pointcut("@annotation(com.genius.workflow.log.ControllerLog)")
	public void controllerAspect() {}

	/**
	 *      * 获取注解中对方法的描述信息 用于Controller层注解      *      * @param joinPoint 切点
	 *      * @return discription     
	 */
	public String getControllerMethodDescription2(JoinPoint joinPoint) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();
		ControllerLog controllerLog = method.getAnnotation(ControllerLog.class);
		String discription = controllerLog.description();
		return discription;
	}

	/**
	 *      * 获取注解中对方法的描述信息 用于service层注解      * @param joinPoint切点     
	 * * @return discription     
	 */
//	public static String getServiceMthodDescription2(JoinPoint joinPoint) {
//		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//		Method method = signature.getMethod();
//		SystemServiceLog serviceLog = method.getAnnotation(SystemServiceLog.class);
//		String discription = serviceLog.description();
//		return discription;
//	}

	/**
	 * 前置通知 用于拦截Controller层记录用户的操作的开始时间
	 * 
	 * @param joinPoint
	 *            切点
	 * @throws InterruptedException
	 */
	@Before("controllerAspect()")
	public void doBefore() {
//		logger.info("进入日志切面前置通知!!");
		Date beginTime = new Date();
		beginTimeThreadLocal.set(beginTime);// 线程绑定变量（该数据只有当前请求的线程可见）
		if (logger.isDebugEnabled()) {// 这里日志级别为debug
			logger.debug("开始计时: {}  URI: {}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(beginTime),
					request.getRequestURI());
		}
		JSONObject userJson = (JSONObject) SecurityUtils.getSubject().getPrincipal();
		currentUser.set(userJson);
	}
	private String argsArrayToString(Object[] paramsArray) {
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (int i = 0; i < paramsArray.length; i++) {
                try {
					Object jsonObj = JSON.toJSON(paramsArray[i]);
					JSONObject json = JSONObject.parseObject(jsonObj.toString());
					if(json.containsKey(UserJson.password)){
						json.put(UserJson.password,"*******");
					}
					if(!json.containsKey("dispatcherServlet")&&!json.containsKey("attributeNames")&&!json.containsKey("servletContext")){
						params += jsonObj.toString() + " ";
					}
				} catch (Exception e) {
					 Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
			            params = paramsMap.toString();
				}
            }
        }
        return params.trim();
    }
	@After("controllerAspect()")
	public void doAfter(JoinPoint joinPoint) {
//		logger.info("进入日志切面后置通知!!");
		JSONObject userJson = currentUser.get();
		if (userJson == null) {
			return;
		}
		String title = "";
		String type = "info";// 日志类型(info:入库,error:错误)
		String remoteAddr = request.getRemoteAddr();// 请求的IP
		String requestUri = request.getRequestURI();// 请求的Uri
		String method = request.getMethod();// 请求的方法类型(post/get)
		String params = "";
		if ("POST".equals(method.toUpperCase())) {
            Object[] paramsArray = joinPoint.getArgs();
            params = argsArrayToString(paramsArray);
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            params = paramsMap.toString();
        }
		
		title = getControllerMethodDescription2(joinPoint);
		// 打印jvm信息
		long beginTime = beginTimeThreadLocal.get().getTime();// 得到线程绑定的局部变量（开始时间）;
		long endTime = System.currentTimeMillis(); // 2、结束时间
		if (logger.isDebugEnabled()) {
			logger.debug("计时结束：{}  URI: {}  耗时： {}   最大内存: {}m  已分配内存: {}m  已分配内存中的剩余空间: {}m  最大可用内存: {}m",
					new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(endTime), request.getRequestURI(),
					TimeDiff.getTimeDiff(endTime - beginTime), Runtime.getRuntime().maxMemory() / 1024 / 1024,
					Runtime.getRuntime().totalMemory() / 1024 / 1024, Runtime.getRuntime().freeMemory() / 1024 / 1024,
					(Runtime.getRuntime().maxMemory() - Runtime.getRuntime().totalMemory()
							+ Runtime.getRuntime().freeMemory()) / 1024 / 1024);
		}
//		logger.info("设置日志信息存储到表中!!");
		Geniuslog log = new Geniuslog();
		log.setLogId(UUID.randomUUID().toString());
		log.setTitle(title);
		log.setType(type);
		log.setRemoteAddr(remoteAddr);
		log.setRequestUri(requestUri);
		log.setMethod(method);
		log.setMapToParams(params);
		log.setException("无异常!");
		log.setUserId(userJson.getString(UserJson.userId));
		Date operateDate = beginTimeThreadLocal.get();
		log.setOperateDate(TimeDiff.getTime(Pattern.yyyyMMddHHmmss, operateDate));
		log.setTimeout(TimeDiff.getTimeDiff(endTime - beginTime));
		
		threadPoolTaskExecutor.execute(new SaveLogThread(log, logService));
        logThreadLocal.set(log);
        logger.info(log.toString());
	}

	@AfterReturning(returning = "ret", pointcut = "controllerAspect()")
	public void doAfterReturning(Object ret) throws Throwable {
		// 处理完请求，返回内容
//		logger.info("==========返回参数日志=========");
//		logger.info("返回接口响应参数:" + ret);
		Geniuslog log = logThreadLocal.get();
		if (log != null) {
			log.setResultParams(ret.toString());
//			logger.info("==========更新日志参数=========");
			new UpdateLogThread(log, logService).start();
			logger.info(log.toString());
		}
	}
	/**
     *  异常通知 记录操作报错日志
     * @param joinPoint
     * @param e
     */
	@AfterThrowing(throwing = "e",pointcut = "controllerAspect()")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
//		logger.info("进入日志切面异常通知!!");
		Geniuslog log = logThreadLocal.get();
		if (log!=null) {
			log.setType("error");
			log.setException(e.toString());
			new UpdateLogThread(log, logService).start();
			logger.info(log.toString());
		}
	}
	/**
	     * 保存日志线程
	     */
	private  class SaveLogThread implements Runnable {
		private Geniuslog log;
		private GeniusLogService logService;
		public SaveLogThread(Geniuslog log, GeniusLogService logService) {
			this.log = log;
			this.logService = logService;
		}
		@Override
		public void run() {
			logService.addLog(log);
		}
	}
	/**
	     * 日志更新线程
	     */
	private  class UpdateLogThread extends Thread {
		private Geniuslog log;
		private GeniusLogService logService;
		public UpdateLogThread(Geniuslog log, GeniusLogService logService) {
			super(UpdateLogThread.class.getSimpleName());
			this.log = log;
			this.logService = logService;
		}
		@Override
		public void run() {
			this.logService.putLog(log);
		}
	}
}
